#pragma once
#include <string>
#include <map>
#include <vector>
#include <iostream>
#include <boost/tokenizer.hpp>
#include <boost/smart_ptr.hpp>

namespace html { 
  namespace dom {
  using namespace std;
  using namespace boost;
  
  struct node;
  struct node_data;
  struct class_accessor;
  struct class_set;

  /// @brief internal type of a shared pointer to node_data
  typedef shared_ptr<node_data> node_ptr;

  /// @brief node data structure
  struct node_data {
    /// @brief initialize with a tag name symbol
    node_data(symbol _tag) : tag_(_tag) {}
    /// @brief copy all data from a given node and link copied children 
    /// of 1st level to a given parent
    void copy_from(const node_data& _node_data,node _parent);
    /// @brief weak pointer to parent node
    typedef weak_ptr<node_data> parent_ptr;
    /// @brief weak pointer to parent
    parent_ptr parent_;
    /// @brief tag name symbol
    symbol tag_;
    /// @brief symbol of attribute 'id'
    symbol id_;
    /// @brief symbols of classes set for this node (attribute 'class')
    class_set classes_;
    /// @brief map of all attributes except id and class
    attrib_map attribs_;
    /// @brief text after the start tag of this node
    string text_after_start_tag_;
    /// @brief child nodes
    nodes_data children_;
    /// @brief text after the end tag of this node
    string text_after_end_tag_;
  };
  /// @brief node in kind of a shared pointer with access for creation and usage
  struct node {
    friend node_data;
    /// @brief default constructor
    node() : data_(new node_data(symbol::nil())) {}
    /// @brief constructor for easy tag creation
    explicit node( symbol _tagname ) : data_(new node_data(_tagname)) {}
    /// @brief recursively clone the node's data into a new node
    /// @return the cloned node
    node clone() const { 
      // create new node
      node n;
      // copy this node's data into new node's data and link children to new node
      n.data_->copy_from(*data_,n);
      // return new node
      return n; 
    }
    /// @brief recursively search for one node by an unique id attribute value
    /// @return the found node
    /// @throw exception if node wasn't found
    node operator[] (const string& _id) {
      node result;
      if( !find_id(_id,result) )
        // id not found
        throw 0;
      return result;
    }
    /// @brief recursively search for one node by an unique id symbol
    /// @return true if found else false 
    bool find_id (symbol _id, node& result ) {
      // walk through all children
      for (nodes_data::iterator it=children().begin(); it!=children().end(); it++) {
        // check if id matches
        if (it->id() == _id) {
          result=*it; 
          return true;
        }
        // search for id in child's children
        if( it->find_id(_id,result) )
          return true;
      }
      return false;
    }
    /// @brief find one or more descendants that match a CSS-like selector
    /// @return found nodes
    nodes operator() (const selector& _selector);
    /// @brief compares the contained text with the given string. Returns 
    /// true, if text matches and no children are present.
    bool operator==( const string& text_ ) {
      // if no children and text equals
      return children().empty() && text_after_start_tag() == text_;
    }
    /// @brief compares the contained text with the given string. Returns 
    /// true, if text doesn't match or children are present.
    bool operator!=( const string& text_ ) {
      // inverse ==
      return !operator==(text_);
    }
    /// @brief recursively dumps the node into an output stream
    void dump(ostream& _os) {
      // check if this is a comment
      if( tag() == comment_symbol ) {
        // begin tag head with the name
        _os << "<!" << (const string&)tag() << text_after_start_tag();
        _os << (const string&)tag() << ">";
      }
      else
      {
        // begin tag head with the name
        _os << "<" << (const string&)tag();
        // write class attributes if existing
        if( !classes().empty() ) {
          _os << " class=\"" << classes() << "\""; 
        }
        // write id if set
        if( id() != symbol::nil() )
          _os << " id=\"" << (const string&)id() << "\"";
        // write other attributes
        for (attrib_map::iterator it=attribs().begin(); it!=attribs().end(); ++it)
          _os << " " << (const string&)it->first << "=" << it->second.quote() << it->second << it->second.quote();
        // close tag immediately if empty or append all children
        if( text_after_start_tag().empty() && children().empty() )
          _os << " />";
        else {
          // close start tag
          _os << ">" << text_after_start_tag() /*<< endl*/;
          // write children
          for (nodes_data::iterator it=children().begin(); it!=children().end(); it++)
            it->dump(_os);
          // write close tag
          _os << "</" << (const string&)tag() << ">";
        }
      }
      // write text after end tag
      if( !text_after_end_tag().empty() )
        _os << text_after_end_tag();
      // new line
      //      _os << endl;
    }
    /// @brief count all the nodes including this and all descendants
    /// @return number of nodes
    int count() const { 
      int n=1;
      for( nodes::const_iterator it=children().begin(); it!=children().end(); it++ )
        n += it->count(); 
      return n;
    }
    /// @brief access tag for read/write
    symbol& tag() { return data_->tag_; }
    /// @brief access tag for read only
    const symbol tag() const { return data_->tag_; }
    /// @brief access id for read/write
    symbol& id() { return data_->id_; }
    /// @brief access id for read only
    const symbol id() const { return data_->id_; }
    /// @brief access classes for read/write
    class_set& classes() { return data_->classes_; }
    /// @brief access classes for read only
    const class_set& classes() const { return data_->classes_; }
    /// @brief access attributes for read/write
    /// @attention When adding attributes to the attrib_map "class" or "id" 
    /// are not redirected data_->id_ and data_->classes_!
    attrib_map& attribs() { return data_->attribs_; }
    /// @brief access attributes for read only
    const attrib_map& attribs() const { return data_->attribs_; }
    /// @brief access children for read/write
    nodes_data& children() { return data_->children_; }
    /// @brief access children for read only
    const nodes_data& children() const { return data_->children_; }
    /// @brief add a comment as child to this node
    /// @attention part of the creation interface - not for recommended for user access!
    /// @return the new node
    node add_comment (const std::string& _text);
    bool has_parent() const {
      return !data_->parent_.expired();
    }
    /// @brief add a new child to this node 
    void operator+=(const node& _node) {
      // check if given node has a parent
      if( _node.has_parent() )
        // clone given node and append the clone to our children container
        children().push_back(_node.clone()); 
      else
        // append given node directly it to our children container
        children().push_back(_node); 
      // set parent of the new child
      children().back().set_parent(*this);
    }
    /// @brief add text to this node (if this node has children, the text 
    /// will be added to text_after_end_tag_ of the last child, if not it 
    /// is added to the text_after_start_tag_ member of this node
    void operator+=(const string& _text) {
      // if this has no children
      if( children().empty() )
        // append text to text after start tag
        text_after_start_tag() += _text;
      else
        // append text to text after end tag
        children().back().text_after_end_tag() += _text;
    }
    /// @brief sets the text in this node <u>and destroys all children!</u>
    void operator=(const string& _text) {
      // set text after start tag
      text_after_start_tag() = _text;
      // destroy all children
      children().clear();
    }
    /// @brief sets the text in this node <u>and destroys all children!</u>
    void operator=(const map<string,string>& _row) {
      if( children().empty() ) {
        // check text after start tag for keys
        {
          map<string,string>::const_iterator itItem = _row.find(text_after_start_tag());
          if( itItem != _row.end() )
            text_after_start_tag() = itItem->second;
        }
        // search classes for all _row items
        for (map<string,string>::const_iterator itItem=_row.begin(); itItem!=_row.end(); ++itItem) {
          // try to find a symbol for the current column name
          symbol sym=symbol_table::find(itItem->first);
          // check if symbol was found
          if (sym != symbol::nil()) {
            // search classes for symbol
            class_set::iterator itClass=classes().find(sym);
            // if class with that symbol was found
            if (itClass != classes().end()) {
              // remove this class symbol
              classes().erase(itClass);
              // and insert the new one
              classes().insert(itItem->second);
            }
          }
        }
        // search attribute values for the symbol
        for (attrib_map::iterator itAttrib=attribs().begin(); itAttrib!=attribs().end(); ++itAttrib) {
          map<string,string>::const_iterator itItem=_row.find(itAttrib->second);
          if (itItem != _row.end() )
            itAttrib->second = itItem->second;
        }
      }
      else {
        for( nodes_data::iterator it=children().begin(); it!=children().end(); ++it )
          *it = _row;
      }
    }
    /// @brief sets the text in this node <u>and destroys all children!</u>
    nodes operator*=(int _n) {
      // result of multiplication
      nodes result;
      // this only works if we're child of a parent
      assert( has_parent() );
      // if multiplicity is one
      if( _n == 1 ) 
        // just add us to the result
        result+=*this;
      // other multiplicity
      else
      {
        // substitute our parent's children - let's call them brothers
        nodes_data& brothers=get_parent().children();
        // search for our child index
        for (size_t i=0; i<brothers.size(); ++i) {
          // if we share the same pointer
          if (brothers[i].data_ == data_) {
            // if multiplicity is zero
            if (_n == 0)
              // detach us from parent
              brothers.erase(brothers.begin()+i);
            else {
              // add us to the result
              result+=*this;
              // clone us for the rest
              for (int j=1; j<_n; j++) {
                // clone us
                result+=clone();
                // add us to parent
                brothers.insert(brothers.begin()+j+i,result.back());
              }
            }
            // we're ready here
            break;
          }
        }
      }
      // return multiplied nodes 
      return result;
    }
    /// @brief set/add an attribute to this node
    /// @attention part of the creation interface - not for recommended for user access!
    void set_attrib (const symbol& _name, const quoted_string& _text, char quote_='\"') {
      // check if symbol is the class symbol
      if( _name == class_symbol ) {
        // prepare classes instance for overwrite
        classes().clear();
        // separate all class names in _text
        tokenizer<char_separator<char> > tok(_text,char_separator<char>(" "));
        for (tokenizer<char_separator<char> >::iterator it=tok.begin(); it!=tok.end(); ++it)
          // insert class from _text
          classes().insert(*it);
      }
      // check if symbol is the id symbol
      else if( _name == id_symbol )
        // overwrite id with text
        id() = _text;
      else
        // store other attribute
        attribs()[_name].set(_text,quote_);
    }
    /// @brief append a child node to this node
    /// @attention part of the creation interface - not for recommended for user access!
    /// @return the new node
    node& add_child (symbol _tagname) {
      // append new node based on _tagname
      operator+=(node(_tagname));
      // return the new node
      return children().back();
    }
    node get_parent() {
      return data_->parent_.lock();
    }
  protected:
    node( const node_ptr& _ptr ) : data_(_ptr) {}
    /// @brief access text after start tag for read/write
    string& text_after_start_tag() { return data_->text_after_start_tag_; }
    /// @brief access text after start tag for read only
    const string& text_after_start_tag() const { return data_->text_after_start_tag_; }
    /// @brief access text after end tag for read/write
    string& text_after_end_tag() { return data_->text_after_end_tag_; }
    /// @brief access text after end tag for read only
    const string& text_after_end_tag() const { return data_->text_after_end_tag_; }
    void set_parent(const node& _parent) {
      data_->parent_ = _parent.data_;
    }

    /// @brief shared pointer to node_data
    node_ptr    data_;
  };
  //////////////////////////////////////////////////////////////////////////
  // special derivation
  //////////////////////////////////////////////////////////////////////////
  /// @brief node that initializes to represent a comment
  struct comment_node : public node {
    comment_node() : node(comment_symbol) {}
  };

  /// @brief node derivate that holds an additional DOCTYPE and XML version member
  struct root_node : public node {
    /// @brief initializes the root node
    root_node(
      const string& _name="html",
      const string& _doc_type="",
      const string& _xml_version="" )
      : node(_name)
      , root_info_(new root_info()) {
      root_info_->doc_type_ = _doc_type;
      root_info_->xml_version_ = _xml_version;
    }
    /// @brief recursively clone the root node 
    root_node clone() const { 
      // copy root node's base data to a new instance
      root_node n(tag(),root_info_->doc_type_,root_info_->xml_version_);
      // copy node's data and link children to the new root node
      n.data_->copy_from(*data_,n);
      // return new root node
      return n; 
    }
    /// @brief dump node recursively
    inline void dump (ostream& _os) {
      // put out DOCTYPE if set
      if (!root_info_->doc_type_.empty())
        _os << "<!DOCTYPE " << root_info_->doc_type_ << ">" << endl;
      // put out xml version if set
      if (!root_info_->xml_version_.empty())
        _os << "<xml version=\"" << root_info_->xml_version_ << "\">" << endl;
      // dump the rest of the node
      node::dump(_os);
    }
    /// @brief set the DOCTYPE
    /// @attention part of the creation interface - not for recommended for user access!
    /// @todo not well implemented!
    void set_doc_type(const string& _doc_type) { root_info_->doc_type_=_doc_type; }
    /// @brief set the DOCTYPE
    /// @attention part of the creation interface - not for recommended for user access!
    /// @todo some other xml attributes missing?
    void set_xml_version(const string& _xml_version) { root_info_->xml_version_=_xml_version; }
  private:
    /// @brief additional root information
    struct root_info {
      /// @brief content of <!DOCTYPE ... >
      string doc_type_;
      /// @brief content of <?xml version="..." ?>
      string xml_version_;
    };
    /// @brief shared pointer to the additional root information
    shared_ptr<root_info> root_info_;
  };

  /// @brief collects information of to nodes into one.
  /// @todo explain sense of this operator
  inline node operator, (node& _left,const node& _right) {
    // check if left node has no tag name (that's if it only collects 
    // attributes but has no children)
    if (_left.tag() == symbol::nil())
    {
      // if right tag also is a attribute collector only
      if (_right.tag() == symbol::nil()) {
        // copy attributes
        _left.attribs().insert(_right.attribs().begin(),_right.attribs().end());
        // set id (if it has not set before)
        _left.id() |= _right.id();
        // copy classes
        _left.classes().insert(_right.classes().begin(),_right.classes().end());
      }
      else
        // transfer only children from right to left
        _left += _right;
      return _left;
    }
    // or if it has
    else
    {
      // create a new parent
      node parent;
      // add both as children to this parent
      parent += _left;
      parent += _right;
      // return the new parent
      return parent;
    }
  }
  /// @brief add a text to a given node
  /// @todo explain sense of this operator
  inline node operator, (node& _left,const string& _text) {
    // check if left node has no tag name (that's if it only collects 
    // attributes but has no children)
    if (_left.tag() == symbol::nil())
    {
      // add text to left node
      _left += _text;
      return _left;
    }
    else
    {
      // create an new parent
      node parent;
      // add left as children to this parent
      parent += _left;
      // add text to the parent
      parent += _text;
      return parent;
    }
  }
} }
